﻿using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using Coding4Fun.BoxingBots.Common;

namespace Coding4Fun.BoxingBots.Communication
{
	public class SocketClient<TSend, TReceive>	where TSend : GamePacket
												where TReceive : GamePacket, new()
	{
		public event EventHandler<ConnectionEventArgs> ConnectionCompleted;
		public event EventHandler<PacketReceivedEventArgs<TReceive>> PacketReceived;

		public bool IsConnected { get { return _connected && _client.Connected ; } }

		private readonly TcpClient _client;
		private bool _connected;
		private Thread _readerThread;
		private readonly SynchronizationContext _context;

		public SocketClient()
		{
			_context = SynchronizationContext.Current;
			_client = new TcpClient();
		}

		public SocketClient(TcpClient client)
		{
			_context = SynchronizationContext.Current;

			_client = client;
			_connected = true;

			_readerThread = new Thread(ReaderThread);
			_readerThread.IsBackground = true;
			_readerThread.Start();
		}

		public void Connect(string address, int port)
		{
			_client.BeginConnect(address, port, OnSocketConnectCompleted, null);
		}

		private void OnSocketConnectCompleted(IAsyncResult ar)
		{
			_connected = _client.Connected;

			if(ConnectionCompleted != null)
				ConnectionCompleted(this, new ConnectionEventArgs { Connected = _connected });

			if(_connected)
			{
				_readerThread = new Thread(ReaderThread);
				_readerThread.IsBackground = true;
				_readerThread.Start();
			}
		}

		public void Disconnect()
		{
			if(_client != null)
				_client.Close();
		}

		private void ReaderThread()
		{
			NetworkStream ns = _client.GetStream();
			BinaryReader reader = new BinaryReader(ns);

			while(IsConnected)
			{
				TReceive packet = new TReceive();
				packet.FromBinaryReader(reader);

				EventHelper.RaiseEvent(this, _context, PacketReceived, new PacketReceivedEventArgs<TReceive> { Packet = packet });
			}
		}

		public bool Send(TSend packet)
		{
			try
			{
				if (IsConnected)
				{
					NetworkStream stream = _client.GetStream();
					byte[] data = packet.ToByteArray();
					stream.BeginWrite(data, 0, data.Length, WriteCompleted, stream);
				}
				else
				{
					_connected = false;
					return false;
				}
			}
			catch(IOException ex)
			{
				Debug.WriteLine(ex.ToString());
				_connected = false;
			}
			catch(ObjectDisposedException ex)
			{
				Debug.WriteLine(ex.ToString());
				_connected = false;
			}

			return true;
		}

		private void WriteCompleted(IAsyncResult ar)
		{
			try
			{
				if(IsConnected)
				{
					NetworkStream ns = (NetworkStream)ar.AsyncState;
					ns.EndWrite(ar);
				}
			}
			catch(IOException ex)
			{
				Debug.WriteLine(ex.ToString());
				_connected = false;
			}
			catch(ObjectDisposedException ex)
			{
				Debug.WriteLine(ex.ToString());
				_connected = false;
			}
		}

		public void Close()
		{
			if(_client.Connected)
				_client.Close();
		}
	}
}
